Skip to content

Fix infinite loop in xt::roll on arrays with a zero-length dimension#2922

Merged
JohanMabille merged 1 commit into
xtensor-stack:masterfrom
f14XuanLv:fix/roll-empty-array
May 29, 2026
Merged

Fix infinite loop in xt::roll on arrays with a zero-length dimension#2922
JohanMabille merged 1 commit into
xtensor-stack:masterfrom
f14XuanLv:fix/roll-empty-array

Conversation

@f14XuanLv
Copy link
Copy Markdown
Contributor

Checklist

  • The title and commit message(s) are descriptive.
  • Small commits made to fix your PR have been squashed to avoid history pollution.
  • Tests have been added for new features or bug fixes.
  • API of new functions and classes are documented. (N/A — bug fix only, no API change)

Description

xt::roll enters an infinite loop (and then divides by zero) when the input
has a zero-length axis:

  • No-axis overload roll(e, shift) when e.size() == 0:
    while (shift < 0) shift += flat_size; loops forever with flat_size == 0,
    then shift %= 0 is undefined behaviour.
  • Axis-aware overload roll(e, shift, axis) when the rolled axis has length 0:
    same issue on axis_dim == 0.

Fix

For a shape $s = (s_0, \dots, s_{n-1})$,
if $\exists k: s_k = 0$, then $\mathrm{roll}(a, \mathrm{shift}, \mathrm{axis}) = a$,
i.e. roll is the identity.

In both overloads, after constructing cpy = empty_like(e), return cpy
early when cpy.size() == 0. The no-axis overload's hand-rolled
std::accumulate(shape, 1L, multiplies<size_t>()) is replaced by cpy.size()
(numerically equivalent, and O(1) on contiguous layouts).

When the rolled axis is non-empty but another axis has length 0
(e.g. shape={3,0,4} rolled along axis 0), the original code didn't loop —
detail::roll's inner loops became no-ops because the stride product
collapses to 0 — but the recursion was wasted work. The widened guard
cpy.size() == 0 skips it cleanly. Observable output is unchanged.

Tests

Three regression cases in test_xmanipulation.cpp, one per code path:

Shape Axis Path exercised
{0} (ravel) no-axis overload's empty-input guard
{3, 0} 1 axis-aware overload, rolled axis itself is 0
{3, 0, 4} 0 axis-aware overload, other axis is 0

All test_xmanipulation tests pass: 28 cases, 188 assertions. Verified
against numpy.roll, which returns a same-shape empty array for these inputs.

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 28, 2026

Merging this PR will not alter performance

✅ 255 untouched benchmarks


Comparing f14XuanLv:fix/roll-empty-array (10a24af) with master (9575b39)

Open in CodSpeed

Copy link
Copy Markdown
Member

@JohanMabille JohanMabille left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix!

@JohanMabille JohanMabille merged commit c3f71fb into xtensor-stack:master May 29, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants